Skip to content

Pass OpenAPI document to Context class constructor - #1638

Closed
pmcelhaney with Copilot wants to merge 22 commits into
mainfrom
copilot/pass-openapi-document-to-context
Closed

Pass OpenAPI document to Context class constructor#1638
pmcelhaney with Copilot wants to merge 22 commits into
mainfrom
copilot/pass-openapi-document-to-context

Conversation

Copilot AI commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Summary

The Context class in _.context.ts had no way to access the loaded OpenAPI document at construction time. This change passes it as openApiDocument in the constructor options object, alongside the existing loadContext and readJson helpers. When the OpenAPI spec changes on disk, the OpenApiDocument class updates its own properties in-place (via watch()load()), and the Context automatically sees the latest spec through a stable read-only Proxy.

// routes/_.context.ts
export class Context {
  constructor({ openApiDocument, loadContext, readJson }) {
    this.spec = openApiDocument; // stays up-to-date as the spec changes on disk
  }
}
Original Prompt

Pass the loaded OpenAPI document to the Context class constructor so route handlers can access spec metadata at runtime.

Manual acceptance tests

  • _.context.ts constructor receives openApiDocument and it matches the loaded spec (e.g. openApiDocument.paths contains the routes defined in the YAML)
  • Existing _.context.ts files with no constructor argument continue to work unchanged
  • loadContext and readJson still work correctly alongside openApiDocument
  • When no OpenAPI document is provided (e.g. openApiPath === "_"), openApiDocument is an empty object — not undefined or an error
  • After editing the OpenAPI spec file while the server is running, a Context that stored openApiDocument reflects the updated spec through the same proxy reference
  • Attempting to set or delete properties on openApiDocument inside a Context does not throw and has no effect (read-only)

Tasks

  • Added optional openApiDocument?: OpenApiDocument parameter (6th position, after scenariosPath and scenarioRegistry) to ModuleLoader constructor; imported OpenApiDocument type from openapi-document.ts
  • Created a simplified read-only Proxy wrapping the provided OpenApiDocument instance (or {} when none is given); since the OpenApiDocument class updates its own properties in-place on reload, no openApiDocumentRef indirection or setOpenApiDocument method is needed
  • The Proxy returns false for set and deleteProperty traps, making it read-only; reads delegate to the underlying object so in-place updates are immediately visible
  • Passed this.openApiDocumentProxy into the Context constructor options object
  • In app.ts, forwarded openApiDocument as the 6th argument to ModuleLoader in both counterfact() and createMswHandlers(); removed the now-redundant codeGenerator.generate event listener (spec reloads are handled by openApiDocument?.watch())
  • Removed duplicate optional OpenApiDocument interface from dispatcher.ts; kept main's required-paths version
  • Merged origin/main changes: ScenarioRegistry support in ModuleLoader, writeApplyContextType on context changes, openApiDocument?.watch() / openApiDocument?.stopWatching() lifecycle, and the new OpenApiDocument class in openapi-document.ts
  • Fixed TypeScript error in src/repl/route-builder.ts: extracted paths ?? {} to a local variable before Object.keys() so the loop body always operates on a defined value
  • Updated tests: fixed ModuleLoader constructor call positions (undefined, undefined, openApiDocument), replaced setOpenApiDocument tests with in-place mutation tests verifying the proxy reflects document changes; added tests "reflects in-place document updates through the proxy" and "proxy reflects mutated document properties"
  • Added minor changeset

@changeset-bot

changeset-bot Bot commented Apr 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7545c07

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
counterfact Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI changed the title [WIP] Pass the OpenAPI document to the constructor of the context class Pass OpenAPI document to Context class constructor Apr 3, 2026
Copilot AI requested a review from pmcelhaney April 3, 2026 23:00

@pmcelhaney pmcelhaney left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the document is changed on disk make sure the object passed to the context object receives the updates.

One way to do that is to remove all top level keys and replace them.

Copilot AI commented Apr 3, 2026

Copy link
Copy Markdown
Contributor Author

If the document is changed on disk make sure the object passed to the context object receives the updates.

One way to do that is to remove all top level keys and replace them.

Done in commit 2cfdd55. ModuleLoader now has a setOpenApiDocument() method that removes all top-level keys from the stored document object and replaces them with the new document's keys — keeping the same object reference so existing Context instances see the update automatically.

In app.ts, the codeGenerator "generate" event (fired whenever the OpenAPI spec changes on disk) now reloads the document and calls moduleLoader.setOpenApiDocument(newDoc) + updates dispatcher.openApiDocument.

Copilot AI requested a review from pmcelhaney April 3, 2026 23:10

@pmcelhaney pmcelhaney left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be smarter to assign openApiDocument reference to a Proxy that is read only.

Copilot AI commented Apr 3, 2026

Copy link
Copy Markdown
Contributor Author

Maybe it would be smarter to assign openApiDocument reference to a Proxy that is read only.

Done in commit 4c25b46. The openApiDocument passed to Context is now a read-only Proxy backed by a { current } ref. When setOpenApiDocument(newDoc) is called, it just swaps ref.current = newDoc — no mutation of the document object itself. All reads through the Proxy automatically reflect the latest document, and any attempt to set or delete properties returns false.

Copilot AI requested a review from pmcelhaney April 3, 2026 23:41

@pmcelhaney pmcelhaney left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .current property seems unnecessary. The object could just be assigned to openApiDocumentRef.

I don't understand why you wouldn't update the openApiDocument if it wasn't assigned originally. Make openApiDocumentRef default to an empty object if necessary.

If there are good reasons for either of these decisions, don't make changes. Just explain the reasoning.

Copilot AI commented Apr 4, 2026

Copy link
Copy Markdown
Contributor Author

The .current property seems unnecessary. The object could just be assigned to openApiDocumentRef.

I don't understand why you wouldn't update the openApiDocument if it wasn't assigned originally. Mak...

Done in commit a20b89c.

  • Dropped the .current wrapper — openApiDocumentRef now holds the document directly. The Proxy captures self = this in its handler closures, so every read goes through self.openApiDocumentRef and picks up any reassignment automatically.
  • openApiDocumentRef defaults to {} when no document is provided, so the Proxy is always created and setOpenApiDocument always works without any early-return guard.

Copilot AI requested a review from pmcelhaney April 4, 2026 00:27
@pmcelhaney
pmcelhaney marked this pull request as ready for review April 4, 2026 01:06
Copilot AI requested a review from pmcelhaney April 6, 2026 20:30
@pmcelhaney

Copy link
Copy Markdown
Collaborator

@copilot TypeScript compilation is failing

src/server/module-loader.ts:53:5 - error TS2741: Property 'paths' is missing in type '{}' but required in type 'OpenApiDocument'.

53 openApiDocument: OpenApiDocument = {},
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/server/dispatcher.ts:60:3
60 paths: {
~~~~~
'paths' is declared here.

Found 1 error in src/server/module-loader.ts:53

Copilot AI commented Apr 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot TypeScript compilation is failing

...

Fixed in commit 05a3c31. Made paths optional in the OpenApiDocument interface in dispatcher.ts — an empty {} is semantically valid when no spec is loaded, and the for...in loop in dispatcher.ts already handles undefined paths gracefully.

@pmcelhaney

Copy link
Copy Markdown
Collaborator

@copilot resolve conflicts

Copilot AI commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve conflicts

Done in commits e6096f0 and bbe62d6. The merge was already in place but it introduced a TypeScript error: making paths optional in OpenApiDocument broke route-builder.ts, which passed the possibly-undefined value to Object.keys(). Fixed by extracting paths ?? {} to a local variable so the loop body always operates on a defined value — no optional chaining needed. yarn tsc --noEmit now passes cleanly.

@pmcelhaney

Copy link
Copy Markdown
Collaborator

@copilot there are still merge conflicts

Copilot AI commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@copilot there are still merge conflicts

Done in commit 023f8b7. Merged origin/main (which introduced OpenApiDocument as a self-updating class, ScenarioRegistry, and more) and resolved all three conflicts:

  • dispatcher.ts: removed our duplicate optional interface; kept main's required-paths version
  • module-loader.ts: combined main's scenariosPath/scenarioRegistry parameters with our openApiDocument proxy; simplified the proxy since the new class updates its properties in-place on reload
  • app.ts: pass both scenarios args and openApiDocument to ModuleLoader; dropped the now-redundant codeGenerator.generate listener (superseded by openApiDocument?.watch())

yarn tsc --noEmit passes cleanly and all 20 module-loader tests pass.

@pmcelhaney

Copy link
Copy Markdown
Collaborator

Somehow the call stack is getting blown when I try to use this. It's not a very important feature right now so I'm going to let it go.

@pmcelhaney pmcelhaney closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pass the OpenAPI document to the constructor of the context class

3 participants